#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
#include <stdlib.h>

#include "../Common/include/version.h"
#include "../Common/include/operatingsystem.h"

#include "parsetree.h"
#include "parsers.h"
#include "symboltable.h"
#include "codegenerator.h"
#include "operators.h"

#include "../Common/anyoption/anyoption.h"

#ifdef _WIN32
    #define POPEN _popen
    #define PCLOSE _pclose
#else
    #define POPEN popen
    #define PCLOSE pclose
#endif

using namespace std;

bool verbose = false;

// TODO: Recursive functions (especially the variables, which are usually global, that has to be saved in the local c scope before calling)
// TODO: the everlasting effort for arrays
// TODO: Name clashing with modules... extend generateIdentifier to append an ID for a module

std::string exec(string cmd, bool hideStdOut)
{
    if (hideStdOut)
        cmd += " 2>/dev/null";
    FILE* pipe = POPEN(cmd.c_str(), "r");
    if (!pipe)
    {
        TException up(100, "Could not find the Thinder Parser. Please check your installation.");
        throw up;
    }
    char buffer[128];
    std::string result = "";
    while(!feof(pipe)) 
    {
        if(fgets(buffer, 128, pipe) != NULL)
            result += buffer;
    }
    PCLOSE(pipe);
    return result;
}

std::string exec(string cmd)
{
    return exec(cmd, false);
}

std::string readFile(string filename)
{
    string contents = "";
    string line;
    ifstream myfile(filename.c_str());
    if (myfile.is_open())
    {
        while (! myfile.eof() )
        {
            getline(myfile, line);
            contents += line + "\n";
        }
        myfile.close();
    }
    else 
    {
        TException up(101, "Unable to open file \"" + filename + "\"");
        throw up;
    }

    return contents;
}

bool fileReadable(string filename)
{
    ifstream myfile(filename.c_str());
    if (myfile.is_open())
    {
        myfile.close();
        return true;
    }
    else 
    {
        return false;
    }
}

void setAnyOption(AnyOption &opt, int argc, char *argv[])
{    
    string s = string("Usage: " + string(argv[0]) + " [options] file...");
    opt.addUsage(s.c_str());
    opt.addUsage("Options:");
    opt.addUsage("  -h  --help            Prints this information");
    opt.addUsage("  -v  --verbose         Turn on verbosity");
    opt.addUsage("      --symboltable     Print symbol table");
    opt.addUsage("      --simulate        Does not actually compile");
    opt.addUsage("  -o  --output <file>   Place the output into <file>");
    opt.addUsage("  -G  --keep-generated  Keep the generated code");
    opt.addUsage("  -S  --dont-compile    Stop after generating code for compilation");
    opt.addUsage("  -m  --module          Build module code");
    opt.addUsage("  -V  --version         Print version information");
    opt.addUsage("");
    opt.addUsage("For bug reporting instructions, please see:");
    opt.addUsage("<http://www.thinder.org/bugs>");
    
    opt.setFlag("help", 'h');
    opt.setFlag("verbose", 'v');
    opt.setFlag("symboltable");
    opt.setFlag("fullsymboltable");
    opt.setFlag("simulate");
    opt.setFlag("keep-generated", 'G');
    opt.setFlag("dont-compile", 'S');
    opt.setOption("output", 'o');
    opt.setFlag("version", 'V');
    opt.setFlag("module", 'm');
    
    opt.processCommandArgs(argc, argv);

}

int main(int argc, char *argv[]) 
{
    AnyOption opt;
    setAnyOption(opt, argc, argv);

    if (!opt.hasOptions() || opt.getFlag("help") || opt.getFlag('h')) 
    {
        opt.printUsage();
        return EXIT_SUCCESS;
    }
    
    if (opt.getFlag("version") || opt.getFlag('V')) 
    {
        std::cout << "Experimental Thinder Compiler (" << argv[0] << ") (" + string(VERSION_INFORMATION) + ") " + string(MAJOR) + "." + string(MINOR) + "." + string(BUILD) << std::endl;
        std::cout << "Last modification on " + string(__TIMESTAMP__) + ", compiled on " + string(__DATE__) + " " + string(__TIME__) << std::endl;
        std::cout << "Copyright (c) 2009-2010 Bas du Pre." << std::endl;
        std::cout << "This is free software; see the source for copying conditions.  There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." << std::endl;
        std::cout << std::endl;
        return EXIT_SUCCESS;
    }
    
    bool module = false;
    if (opt.getFlag("module") || opt.getFlag('m'))
        module = true;

    vector<string> inputfiles;
    vector<string> outputfiles;
        
    for(int i = 0; i < opt.getArgc(); i++)
    {
        inputfiles.push_back(opt.getArgv(i));
        outputfiles.push_back(inputfiles.back().substr(0, inputfiles.back().find_last_of('.')));
    }
    
    if (inputfiles.size() == 0)
    {
        opt.printUsage();
        return EXIT_FAILURE;
    }
    
    string outputfile = outputfiles[0];
    if (module)
    {
        outputfile = "lib" + outputfile + ".so";
    }
    
    bool fail = false;
    for (unsigned int i = 0; i < inputfiles.size(); i++)
    {
        if (!fileReadable(inputfiles[i]))
        {
            fail = true;
            std::cerr << argv[0] <<  ": could not read file \"" << inputfiles[i] << "\"" << std::endl;
        }
    }
    
    if (fail)
        return EXIT_FAILURE;

    string path = argv[0];
    // chop off the executable name
    if (path.find(PATH_SEPARATOR) == string::npos)
        path = "";
    else
        path = path.substr(0, path.find_last_of(PATH_SEPARATOR) + 1);
        
    if (opt.getValue('o') != NULL || opt.getValue("output") != NULL)
    {
        outputfile = opt.getValue('o');
    }
    
    string tp = exec(path + "parser \"" + inputfiles[0] + "\"");
    
    Thinder::ParseTree::Thinder thinder;
    bool parsed = false;
#ifndef _DEBUG
    try 
    {
#endif
        thinder = Thinder::parseThinder(tp);
        parsed = true;
        string headers = "";
        
        SymbolTable symbolTable = buildSymbolTable(thinder);
        OperatorMap operatorMap = processOperators(thinder, symbolTable);
        
        if (opt.getFlag("symboltable"))    
            cout << printSymbolTable(symbolTable) << "\n";
            
        string moduleCode = "";
        if (module)
        {
            moduleCode += "// REGION: module code\n";
            moduleCode += generateModuleCode(thinder, symbolTable, operatorMap);
            moduleCode += "// ENDREGION\n\n";
        }
        
        // process imports
        string linkflags = "";
        for (unsigned int i = 0; i < thinder.imports.size(); i++)
        {
            ParseTree::Name import = thinder.imports[i];
            string importString = printName(import);
            string ret = exec(path + "modtool --where-is \"" + importString + "\"", true);
            if (ret == "")
            {
                std::cerr << "Error: could not find import \"" + importString + "\"" << std::endl;
                return EXIT_FAILURE;
            }
            // if there are multiple files, just take the first one
            if (ret.find("\n") != string::npos) ret = ret.substr(0, ret.find("\n"));
            headers += exec(path + "modtool --limit \"" + importString + "\" --cheader \"" + ret + "\"", true);
                        
            SymbolTable modSymbolTable = parseSymbolTable(exec(path + "modtool --limit \"" + importString + "\" --symboltable \"" + ret + "\"", true));
            
            string retfilename = ret.substr(ret.find_last_of(PATH_SEPARATOR) + 1);
            string retpath = ret.substr(0, ret.length() - retfilename.length());
            // remove lib and .so from the filename
            retfilename = retfilename.substr(3, retfilename.length() - 6);
            
            linkflags += "-L\"" + retpath + "\" -l" + retfilename + " ";
            
            mergeSymbolTable(symbolTable, modSymbolTable);
            addSymlink(symbolTable, import, toName(import.back().identifier));
        }
        
        if (opt.getFlag("fullsymboltable"))    
            cout << printSymbolTable(symbolTable) << "\n";
        
        if (!opt.getFlag("simulate")) 
        {
            string codeheader = "// Generated by the Experimental Thinder Compiler " + string(MAJOR) + "." + string(MINOR) + "." + string(BUILD) + "\n\n";
            codeheader += "// THIS FILE IS GENERATED BY A TOOL. CHANGES MAY RESULT IN UNEXPECTED BEHAVIOUR.\n";
            codeheader += "// ANY CHANGES TO THIS FILE WILL BE LOST AT REGENERATION.\n\n";
            string cincludes = "#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"Common/include/runtime.h\"\n\n";
            string code = cincludes + moduleCode + generateCode(thinder, symbolTable, operatorMap);
        
            ofstream output;
            output.open((outputfiles[0] + ".tc").c_str());
            output << codeheader << endl;
            output << headers << endl;
            output << code << endl;
            output.close();
            
            // TODO: backend configuration
            
            int ret;
            if (!opt.getFlag("dont-compile") && !opt.getFlag('S'))
            {
                if (module)
                {
                    string ns = printName(thinder.ns);
                    string newOutputFile = outputfile;
                    if (newOutputFile.substr(0, 3) == "lib")
                        newOutputFile = newOutputFile.substr(3);
                    if (newOutputFile.substr(newOutputFile.length() - 3) == ".so")
                        newOutputFile = newOutputFile.substr(0, newOutputFile.length() - 3);
                    string soname = "libthinder." + ns + "." + newOutputFile + ".so";
                    if (verbose)
                        std::cout << "Module soname: " << soname << std::endl;
                    ret = system(string("gcc -g -x c " + linkflags + " -fPIC -shared -Wl,-soname," + soname + " -o \"" + outputfile + "\" \"" + outputfiles[0] + ".tc\" -lc 2> .gcc_error.log").c_str());
                }
                else
                    ret = system(string("gcc -g -x c " + linkflags + " \"" + outputfiles[0] + ".tc\" -o \"" + outputfile + "\" 2> .gcc_error.log").c_str());
                switch (ret)
                {
                    case EXIT_SUCCESS:
                        if (readFile(".gcc_error.log").length() > 5)              
                        {
                            std::cerr << "GCC compiled successful but with warnings: " << std::endl;
                            std::cerr << readFile(".gcc_error.log");
                        }
                        remove(".gcc_error.log");
                        break;
                    default:
                    case EXIT_FAILURE:
                        std::cerr << "GCC returned an error: " << std::endl;
                        std::cerr << readFile(".gcc_error.log");
                        break;
                }
            }
            
            if (!opt.getFlag("keep-generated") && !opt.getFlag('G'))
            {
                remove((outputfiles[0] + ".tc").c_str());
            }
            
        }
#ifndef _DEBUG        
    } 
    catch (TException &e)
    {
        cout << "Exception raised: " << e.message << '\n';
        if (!parsed)
            cout << tp;
    }
#endif

    return 0;
}

